'''
Created on Feb 10, 2012

@author: chenglee
'''

import re

EMAIL_ADDRESS_REGEX = re.compile(
    '^[-!#$%&\'*+\\.\/0-9=?A-Z^_`{|}~]+@([-0-9A-Z]+\.)+([0-9A-Z]){2,4}$',
    re.IGNORECASE)

def validate(obj):
  '''Performs validation and returns a dict where the key is the field name and
  the value is a list of ValidationError'''
  errors_map = {}
  for key, value in obj.__class__.__dict__.iteritems():
    if isinstance(value, validator):
      errors = value.validate(obj, key)
      if errors:
        errors_map[key] = [type(error) for error in errors]

  return errors_map

# TODO(chenglee): find a better name for this function
def validate_result_as_flat_list(obj):
  '''Performs validation and returns a list of ValidationError'''
  errors = []
  for key, value in obj.__class__.__dict__.iteritems():
    if isinstance(value, validator):
      error = value.validate(obj, key)
      if error:
        errors.extend(error)

  return errors


class ValidationError(Exception):
  def __init__(self, field_name, *args, **kargs):
    super(ValidationError, self).__init__(args, kargs)
    self.field_name = field_name


class validator(object):
  '''Validator constructor that takes in validators as argument, e.g.

  validator.validator(
      validator.required(),
      validator.not_empty(),
      validator.is_type(str),
      validator.length(10))'''

  def __init__(self, *validators):
    self.validators = list(validators)

  def validate(self, obj, attr):
    '''Returns a list of ValidationError'''
    errors = []
    for val in self.validators:
      error = val(obj, attr)
      if error:
        errors.append(error)

    return errors


class Builder(validator):
  '''Validator builder that allows shorter syntax, e.g.

  validator.Builder().required().not_empty().is_type(str).length(10)

  This style usually yields shorter code, especially for entities with lots
  of fields.'''

  def required(self):
    self.validators.append(required())
    return self

  def not_empty(self):
    self.validators.append(not_empty())
    return self

  def is_type(self, expected_type):
    self.validators.append(is_type(expected_type))
    return self

  def length(self, max_length, min_length=0):
    self.validators.append(length(max_length, min_length=min_length))
    return self

  def in_range(self, min_value, max_value):
    self.validators.append(in_range(min_value, max_value))
    return self

  def in_values(self, values):
    self.validators.append(in_values(values))
    return self

  def is_email(self):
    self.validators.append(is_email())
    return self


class RequiredValidationError(ValidationError):
  def __repr__(self):
    return '"%s" is required' % self.field_name


def required():
  def _check_required(obj, attr):
    value = hasattr(obj, attr) and getattr(obj, attr)
    if isinstance(value, validator):
      # This means the attribute has not been assigned at 'self' scope
      return RequiredValidationError(attr)

    if not hasattr(obj, attr):
      return RequiredValidationError(attr)

    return None
  return _check_required


class NotEmptyValidationError(ValidationError):
  def __repr__(self):
    return '"%s" should not be empty' % self.field_name


def not_empty():
  def _check_not_empty(obj, attr):
    value = hasattr(obj, attr) and getattr(obj, attr)
    if isinstance(value, validator):
      # This means the attribute has not been assigned at 'self' scope
      return NotEmptyValidationError(attr)

    if not hasattr(obj, attr) or not getattr(obj, attr):
      return NotEmptyValidationError(attr)

    return None
  return _check_not_empty


class IncorrectTypeValidationError(ValidationError):
  def __init__(self, field_name, expected_type, *args, **kargs):
    super(IncorrectTypeValidationError, self).__init__(args, kargs)
    self.field_name = field_name
    self.expected_type = expected_type

  def __repr__(self):
    return '"%s" is not %s' % (self.field_name, self.expected_type)


def is_type(expected_type):
  def _check_is_type(obj, attr):
    value = hasattr(obj, attr) and getattr(obj, attr)
    if type(value) != expected_type:
      return IncorrectTypeValidationError(attr, expected_type)
    return None

  return _check_is_type


class LengthValidationError(ValidationError):
  def __init__(self, field_name, min_length, max_length, *args, **kargs):
    super(LengthValidationError, self).__init__(args, kargs)
    self.field_name = field_name
    self.min_length = min_length
    self.max_length = max_length

  def __repr__(self):
    return '"' + self.field_name + '" len is not between (%d,%d)' % (
        self.min_length, self.max_length)


class NoLengthValidationError(ValidationError):
  def __repr__(self):
    return '"' + self.field_name + '" does not have len()'


def length(max_length, min_length=0):
  def _check_length(obj, attr):
    value = hasattr(obj, attr) and getattr(obj, attr)
    if isinstance(value, validator):
      return NoLengthValidationError(attr)
    try:
      length = len(value)
      if length < min_length or length > max_length:
        return LengthValidationError(attr, min_length, max_length)
      return None
    except TypeError:
      return NoLengthValidationError(attr)

  return _check_length


class RangeValidationError(ValidationError):
  def __init__(self, field_name, min_value, max_value, *args, **kargs):
    super(RangeValidationError, self).__init__(args, kargs)
    self.field_name = field_name
    self.min_value = min_value
    self.max_value = max_value

  def __repr__(self):
    return '"' + self.field_name + '" range is not between (%d,%d)' % (
        self.min, self.max)


def in_range(min_value, max_value):
  def _check_range(obj, attr):
    value = hasattr(obj, attr) and getattr(obj, attr)
    if value < min_value or value > max_value:
      return RangeValidationError(attr, min, max)
    return None

  return _check_range


class InValuesValidationError(ValidationError):
  def __init__(self, field_name, values, *args, **kargs):
    super(InValuesValidationError, self).__init__(args, kargs)
    self.field_name = field_name
    self.values = values

  def __repr__(self):
    return '"' + self.field_name + '" is not in %s' % self.values


def in_values(values):
  def _check_is_type(obj, attr):
    value = hasattr(obj, attr) and getattr(obj, attr)
    if value not in values:
      return InValuesValidationError(attr, values)
    return None

  return _check_is_type


class EmailValidationError(ValidationError):
  def __repr__(self):
    return '"' + self.field_name + '" is not a valid email'


def is_email():
  def _check_is_email(obj, attr):
    value = hasattr(obj, attr) and getattr(obj, attr)
    if not value or not EMAIL_ADDRESS_REGEX.match(value):
      return EmailValidationError(attr)
    return None
  return _check_is_email
